﻿using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ComponentModel;

using SharpGL.Media3D;
using SharpGL.SceneGraph.Helpers;
using SharpGL.SceneGraph.Quadrics;

namespace SharpGL.SceneGraph.Primitives
{
    /// <summary>
    /// a trend.
    /// </summary>
    [DataContract(IsReference = true)]
    public class Trend : ContextualSceneElement, IRenderable, IFreezable, IVolumeBound
    {
        #region Private Fields

        /// <summary>
        /// The freezable helper.
        /// </summary>
        private FreezableHelper freezableHelper = new FreezableHelper();
        //--------------------------------------------------------------------------------

        /// <summary>
        /// The position of the object.
        /// </summary>
        [DataMember()]
        private Point3D position = new Point3D(0.0f, 0.0f, 0.0f);
        //--------------------------------------------------------------------------------

        /// <summary>
        /// The direction.
        /// </summary>
        [DataMember()]
        private Vector3D direction = new Vector3D(1.0f, 0.0f, 0.0f);
        //--------------------------------------------------------------------------------

        /// <summary>
        /// The factor.
        /// </summary>
        [DataMember()]
        private float factor = 3.0f;
        //--------------------------------------------------------------------------------

        /// <summary>
        /// The object (sphere) used for trend drawing.
        /// </summary>
        [DataMember()]
        private Sphere elementObject = new Sphere();
        //--------------------------------------------------------------------------------

        /// <summary>
        /// The object (cylinder) used for direction drawing.
        /// </summary>
        [DataMember()]
        private Cylinder directionObject = new Cylinder();
        //--------------------------------------------------------------------------------

        #endregion Private Fields

        #region Protected Methods

        /// <summary>
        /// Perform all rendering operation to the provided instance of OpenGL.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        /// <param name="renderMode">The render mode.</param>
        protected virtual void InternalRender(OpenGL gl, RenderMode renderMode)
        {
            //ValidateContextOperation(gl, CurrentOpenGLContext, "render"); 
            
            if (OpenGL.IsValidContext(gl))
            {
                Vector3D baseVector = new Vector3D(0.0f, 0.0f, 1.0f);
                Vector3D rotateVector = Vector3D.CrossProduct(baseVector, direction);
                double angle = Vector3D.AngleBetween(baseVector, direction);

                float[] currentPointSize = new float[] { 0.0f };

                gl.GetFloat(OpenGL.GL_POINT_SIZE, currentPointSize);

                elementObject.Radius = currentPointSize[0] / 2.0f;

                directionObject.TopRadius = 0.0f;
                directionObject.BaseRadius = currentPointSize[0] / 2.0f;
                directionObject.Height = (currentPointSize[0] / 2.0f) * factor;
                
                gl.PushMatrix();

                gl.Translate(position.X, position.Y, position.Z);
                gl.Rotate(angle, rotateVector.X, rotateVector.Y, rotateVector.Z);

                elementObject.Render(gl, renderMode);
                directionObject.Render(gl, renderMode);

                gl.PopMatrix();
            }
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// Called when creating in the OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        protected override void OnCreateInContext(OpenGL gl)
        {
            elementObject.CreateInContext(gl);
            directionObject.CreateInContext(gl);
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// Called when destroy in the OpenGL instance.
        /// </summary>
        protected override void OnDestroyInContext(OpenGL gl)
        {
            elementObject.DestroyInContext();
            directionObject.DestroyInContext();
        }
        //--------------------------------------------------------------------------------

        #endregion Protected Methods

        #region Public Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Trend"/> class.
        /// </summary>
        public Trend()
        {
            Name = "Direction";

            elementObject.QuadricDrawStyle = DrawStyle.Fill;
            elementObject.NormalOrientation = Orientation.Outside;
            elementObject.NormalGeneration = Normals.Smooth;
            elementObject.TextureCoords = false;
            elementObject.Slices = 3;
            elementObject.Stacks = 3;

            directionObject.QuadricDrawStyle = DrawStyle.Fill;
            directionObject.NormalOrientation = Orientation.Outside;
            directionObject.NormalGeneration = Normals.Smooth;
            directionObject.TextureCoords = false;
            directionObject.Slices = elementObject.Slices;
            directionObject.Stacks = elementObject.Stacks;
        }
        //--------------------------------------------------------------------------------

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Render to the provided instance of OpenGL.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        /// <param name="renderMode">The render mode.</param>
        public virtual void Render(OpenGL gl, RenderMode renderMode)
        {
            if (OpenGL.IsValidContext(gl))
            {
                // If we're frozen, use the helper.
                if (freezableHelper.IsFrozen)
                {
                    freezableHelper.Render();
                }
                else
                {
                    InternalRender(gl, renderMode);
                }
            }
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// Freezes this instance using the provided OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public virtual void Freeze(OpenGL gl)
        {
            //  Freeze using the helper.
            freezableHelper.Freeze(gl, this);
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// Unfreezes this instance using the provided OpenGL instance.
        /// </summary>
        /// <param name="gl">The OpenGL instance.</param>
        public virtual void Unfreeze(OpenGL gl)
        {
            //  Unfreeze using the helper.
            freezableHelper.Unfreeze();
        }
        //--------------------------------------------------------------------------------

        #endregion Public Methods

        #region Public Properties

        /// <summary>
        /// Gets the bounding volume.
        /// </summary>
        [Browsable(false)]
        public BoundingVolume BoundingVolume
        {
            get
            {
                // TODO: only create bv when vectors changed.
                return new BoundingVolume(new Rect3D(position.X, position.Y, position.Z, 0.0f, 0.0f, 0.0f));
            }
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// Gets a value indicating whether this instance is frozen.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is frozen; otherwise, <c>false</c>.
        /// </value>
        [Browsable(false)]
        public virtual bool IsFrozen
        {
            get
            {
                return freezableHelper.IsFrozen;
            }
        }
        //--------------------------------------------------------------------------------

        /// Gets or sets the position.
        /// </summary>
        /// <value>
        /// Position.
        /// </value>
        [Description("Position of trend object."), Category("Trend")]
        public Point3D Position
        {
            get { return position; }
            set
            {
                if (value != null)
                    position = value;
                else
                    position = new Point3D(0.0f, 0.0f, 0.0f);

                NotifyPropertyChanged("Position");
            }
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// Gets or sets the direction vector.
        /// </summary>
        /// <value>
        /// The direction vector.
        /// </value>
        [Description("The direction for trend object."), Category("Trend")]
        public Vector3D Direction
        {
            get { return direction; }
            set
            {
                if (value != null)
                    direction = value;
                else
                    direction = new Vector3D(1.0f, 0.0f, 0.0f);

                NotifyPropertyChanged("Direction");
            }
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// Gets or sets the scale factor.
        /// </summary>
        /// <value>
        /// The trend factor.
        /// </value>
        [Description("The scale factor for trend."), Category("Trend")]
        public float Factor
        {
            get { return factor; }
            set
            {
                factor = value;

                NotifyPropertyChanged("Factor");
            }
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// The slices.
        /// </summary>
        public int Slices
        {
            get { return elementObject.Slices; }
            set
            {
                elementObject.Slices = value;
                directionObject.Slices = value;

                NotifyPropertyChanged("Slices");
            }
        }
        //--------------------------------------------------------------------------------

        /// <summary>
        /// The stacks.
        /// </summary>
        public int Stacks
        {
            get { return elementObject.Stacks; }
            set
            {
                elementObject.Stacks = value;
                directionObject.Stacks = value;

                NotifyPropertyChanged("Stacks");
            }
        }
        //--------------------------------------------------------------------------------

        #endregion Public Properties
    }
}
